WIP:Clojure 軽量スレッドを作ってみる
WIP とは
動機
STMが好きなので、STMを活かした軽量スレッドが作れたら面白そうだと思った
ネイティブスレッドの問題
いっぱい作れない
なぜ Go では何百万もの Goroutine を作れるのに Java は数千のスレッドしか作れないのか?
1スレッドで固定長の大きなメモリが必要
コンテキストスイッチが遅い
リング0 にスイッチする必要がある(よく分かっていない)
暇してるスレッドであろうともスイッチをしてしまう
解決策
メモリ消費
ユーザースレッドならば、スタックの動的確保が再現できるので解決
リング0
ユーザースレッドならば、カーネルのスレッドとは関係ないので解決
暇スレッドを見分ける
どうするか?
暇スレッドの見分け方
暇になるタイミングは基本的に IO 待ち。他にもスリープなど
これらはプログラマがあらかじめ判別可能
待ちが発生する処理をプログラマに教えてもらう。
code: clojure
; 軽量スレッドを仮に k-thread とする
(k-thread
; CPUを酷使する忙しい処理
(+ 1 2 3)
; 待ちが発生する処理は k-idle の中に書く
(k-idle
; IO 待ちが発生する、CPUを全然使わない処理
(jdbc/query db-spec "SELECT * FROM users"))
; CPUを酷使する忙しい処理
(+ 1 2 3))
これでスレッドが暇なのか、忙しいのかの判断ができるようになった。
暇スレッドと忙しスレッドの処理
ネイティブスレッドはスレッドプールで管理する。
待ち処理キューと、忙し処理キューを作る。
軽量スレッドが作られたら、まずはそれを忙し処理キューに入れる。
空いているネイティブスレッドがあれば、忙し処理キューから処理を取ってきて作業する。
その途中で待ちが発生すれば、その処理を中止し、待ち処理キューに入れる。
待ちによって処理を中止したネイティブスレッドは、忙し処理キューからまた処理を取ってくる。
待ち処理キューを処理するためのネイティブスレッドは必ず1つ以上確保して、そいつは常に待ち処理をする
待ち処理が終われば、それを忙し処理キューに入れる。
忙し処理キューが少量になりネイティブスレッドが余れば、そのネイティブスレッドを待ち処理に使うこともある
違うネイティブスレッドで跨って処理するが、STMは大丈夫なのか?
スコープごと移動するから大丈夫?
スレッドにさせる「処理」をどう切り分けて、どうデータ化して、キューに入れるかは慎重にやらないと……
メモ
https://www.ibm.com/developerworks/jp/java/library/j-jtp0730/index.html
thread pool を作る時の注意点と、自分で作らん方がいいという勧告
1つのスレッドプールに対して、2つのキューがあるから自作せざるを得なさそう
その他
名前どうしよう
1つの処理に対して busy / idle を切り替えながら継続する処理
待ちが発生する処理は、IO やらスリープやらあらかじめ決まっているので、言語側のコードで待ちが判別できる仕組みを持てば、プログラマが k-idle とかやる必要がなくなってハッピーになれそう
ライブラリ側で busy / idle してて、それを知らずにアプリ側でも busy / idle したら……
busy / idle を無駄に行き来することになる。
やっぱり言語側で対応するのが理想的だなぁ
JSどうしよう
Web Worker が使えるなら使う
シングルスレッドでも、idle 処理を後回しにして、busy 処理を優先的に処理する方法は有効なはず
core/async と比較
チャネルで暇/忙しを判定しているので、最効率を目指すとIO待ちでチャネルを作ることになる(ちゃんと調べてない)ので、書きやすさとしては変わらない気がする
むしろチャネルを作る必要がない分、書くのは楽かもしれない
キューへの移動時間が勿体無い